<!DOCTYPE html>
<html  lang="zh">
<head>
    <meta charset="utf-8" />

<meta name="generator" content="Hexo 3.8.0" />

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />

<title>Effective Objective-C 2.0（第6章） - OBJC.VIP</title>


    <meta name="description" content="编写高质量iOS与OS X代码的52个有效方法第6章 块与大中枢派发">
<meta name="keywords" content="Effective Objective-C 2.0">
<meta property="og:type" content="article">
<meta property="og:title" content="Effective Objective-C 2.0（第6章）">
<meta property="og:url" content="https://objcvip.github.io/EffectiveObjectiveCPart6/index.html">
<meta property="og:site_name" content="OBJC.VIP">
<meta property="og:description" content="编写高质量iOS与OS X代码的52个有效方法第6章 块与大中枢派发">
<meta property="og:locale" content="zh-CN">
<meta property="og:image" content="https://objcvip.github.io/images/og_image.png">
<meta property="og:updated_time" content="2019-09-08T09:20:51.898Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Effective Objective-C 2.0（第6章）">
<meta name="twitter:description" content="编写高质量iOS与OS X代码的52个有效方法第6章 块与大中枢派发">
<meta name="twitter:image" content="https://objcvip.github.io/images/og_image.png">







<link rel="icon" href="/images/favicon.svg">


<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.7.2/css/bulma.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.1/css/all.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Ubuntu:400,600|Source+Code+Pro">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@9.12.0/styles/atom-one-dark.css">


    
    
    
    <style>body>.footer,body>.navbar,body>.section{opacity:0}</style>
    

    
    
    
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/lightgallery@1.6.8/dist/css/lightgallery.min.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/css/justifiedGallery.min.css">
    

    
    

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/outdatedbrowser@1.1.5/outdatedbrowser/outdatedbrowser.min.css">


    
    
    
    

<link rel="stylesheet" href="/css/back-to-top.css">


    
    

    
    
    
    

    
    
<link rel="stylesheet" href="/css/progressbar.css">
<script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>

    
    
    


<link rel="stylesheet" href="/css/style.css">
</head>
<body class="is-1-column">
    <nav class="navbar navbar-main">
    <div class="container">
        <div class="navbar-brand is-flex-center">
            <a class="navbar-item navbar-logo" href="/">
            
                <img src="/images/logo.svg" alt="Effective Objective-C 2.0（第6章）" height="28">
            
            </a>
        </div>
        <div class="navbar-menu">
            
            <div class="navbar-start">
                
                <a class="navbar-item"
                href="/">首页</a>
                
                <a class="navbar-item"
                href="/archives">归档</a>
                
                <a class="navbar-item"
                href="/categories">分类</a>
                
                <a class="navbar-item"
                href="/tags">标签</a>
                
                <a class="navbar-item"
                href="/links">友链</a>
                
                <a class="navbar-item"
                href="/about">关于</a>
                
            </div>
            
            <div class="navbar-end">
                
                
                
                <a class="navbar-item search" title="搜索" href="javascript:;">
                    <i class="fas fa-search"></i>
                </a>
                
            </div>
        </div>
    </div>
</nav>
    
    <section class="section">
        <div class="container">
            <div class="columns">
                <div class="column is-12 has-order-2 column-main"><div class="card">
    
    <div class="card-content article ">
        
        <div class="level article-meta is-size-7 is-uppercase is-mobile is-overflow-x-auto">
            <div class="level-left">
                <time class="level-item has-text-grey" datetime="2018-01-05T16:06:00.000Z">2018-01-06</time>
                
                <div class="level-item">
                <a class="has-link-grey -link" href="/categories/Effective-Objective-C-2-0/">Effective Objective-C 2.0</a>
                </div>
                
                
                <span class="level-item has-text-grey">
                    
                    
                    17 分钟 读完 (大约 2526 个字)
                </span>
                
                
            </div>
        </div>
        
        <h1 class="title is-size-3 is-size-4-mobile has-text-weight-normal">
            
                Effective Objective-C 2.0（第6章）
            
        </h1>
        <div class="content">
            <h2 id="编写高质量iOS与OS-X代码的52个有效方法"><a href="#编写高质量iOS与OS-X代码的52个有效方法" class="headerlink" title="编写高质量iOS与OS X代码的52个有效方法"></a>编写高质量iOS与OS X代码的52个有效方法</h2><h3 id="第6章-块与大中枢派发"><a href="#第6章-块与大中枢派发" class="headerlink" title="第6章 块与大中枢派发"></a>第6章 块与大中枢派发</h3><a id="more"></a>
<h4 id="37-理解“块”这一概念"><a href="#37-理解“块”这一概念" class="headerlink" title="37.理解“块”这一概念"></a>37.理解“块”这一概念</h4><ul>
<li>块是C、C++、Objective-C 中的词法闭包。</li>
<li>块可接受参数，也可返回值。</li>
<li>块可以分配在栈或堆上，也可以是全局的。分配在栈上的块可拷贝到堆里，这样的话，就和标准的 Objective-C 对象一样，具备引用计数了。</li>
</ul>
<h4 id="38-为常用的块类型创建-typedef"><a href="#38-为常用的块类型创建-typedef" class="headerlink" title="38.为常用的块类型创建 typedef"></a>38.为常用的块类型创建 typedef</h4><ul>
<li>以typedef重新定义块类型，可令块变量用起来更加简单。</li>
<li>定义新类型时应遵从现有的命名习惯，勿使其名称与别的类型相冲突。</li>
<li>不妨为同一个块签名定义多个类型别名。如果要重构的代码使用了块类型的某个别名，那么只需修改相应的 typedef 中的块签名即可，无须改动其他typedef。</li>
</ul>
<h4 id="39-用-handler-块降低代码分散程度"><a href="#39-用-handler-块降低代码分散程度" class="headerlink" title="39.用 handler 块降低代码分散程度"></a>39.用 handler 块降低代码分散程度</h4><ul>
<li>在创建对象时，可以使用内联的 handler 块将相关业务逻辑一并声明。</li>
<li>在有多个实例需要监控时，如果采用委托模式，那么经常需要根据传入的对象来切换，而若改用 handler 块来实现，则可直接将块与相关对象放在一起。</li>
<li>设计 API 时如果用到了 handler 块，那么可以增加一个参数，使调用者可通过此参数来决定应该把块安排在哪个队列上执行。</li>
</ul>
<h4 id="40-用块引用其所属对象时不要出现保留环"><a href="#40-用块引用其所属对象时不要出现保留环" class="headerlink" title="40.用块引用其所属对象时不要出现保留环"></a>40.用块引用其所属对象时不要出现保留环</h4><figure class="highlight objc hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="hljs-keyword">void</span>)p_requestCompleted &#123;</span><br><span class="line">    <span class="hljs-keyword">if</span> (_completionHandler) &#123;</span><br><span class="line">        _completionHandler(_downloadedData);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="hljs-keyword">self</span>.completionHandler = <span class="hljs-literal">nil</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>只要下载请求执行完毕，保留环就解除了，而获取器对象也将会在必要时为系统所回收。</p>
<ul>
<li>如果块所捕获的对象直接或间接地保留了块本身，那么就得当心保留环问题。</li>
<li>一定要找个适当的时机解除保留环，而不能把责任推给 API 的调用者。</li>
</ul>
<h4 id="41-多用派发队列，少用同步锁"><a href="#41-多用派发队列，少用同步锁" class="headerlink" title="41.多用派发队列，少用同步锁"></a>41.多用派发队列，少用同步锁</h4><h5 id="锁"><a href="#锁" class="headerlink" title="锁"></a>锁</h5><ol>
<li><p>同步块（synchronization block）</p>
<figure class="highlight objc hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="hljs-keyword">void</span>)synchronizedMethod &#123;</span><br><span class="line">    <span class="hljs-keyword">@synchronized</span>(<span class="hljs-keyword">self</span>) &#123;</span><br><span class="line">          <span class="hljs-comment">//Safe</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>NSLock锁对象  NSRecursiveLock递归锁</p>
<figure class="highlight objc hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">_lock = [[<span class="hljs-built_in">NSLock</span> alloc] init];</span><br><span class="line">- (<span class="hljs-keyword">void</span>)synchronizedMethod &#123;</span><br><span class="line">    [_lock lock];</span><br><span class="line">    <span class="hljs-comment">//Safe</span></span><br><span class="line">    [_lock unlock];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
</ol>
<p>缺陷：</p>
<ul>
<li>在极端情况下，同步块会导致死锁。</li>
<li>效率不见得很高。</li>
<li>直接使用锁对象，一旦遇到死锁，就会非常麻烦。</li>
<li>用atomic来修饰属性，只能提供某种程度的线程安全，无法保证访问该对象时绝对是线程安全的。使用属性时，必然能从中获取到有效值，然而在同一线程上多次调用获取方法（getter），每次获取到的结果未必相同。在两次访问操作之间，其他线程可能会写入新的属性值。</li>
</ul>
<ol start="3">
<li>GCD<br> 串行同步队列（serial synchronization queue）<figure class="highlight objc hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">_syncQueue = dispatch_queue_create(<span class="hljs-string">"com.effectiveObjectivec.syncQueue"</span>,<span class="hljs-literal">NULL</span>);</span><br><span class="line">- (<span class="hljs-built_in">NSString</span> *)someString &#123;</span><br><span class="line">  __block <span class="hljs-built_in">NSString</span> *localSomeString;</span><br><span class="line"> <span class="hljs-built_in">dispatch_sync</span>(_syncQueue, ^&#123;</span><br><span class="line">      localSomeString = _someString;</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="hljs-keyword">return</span> localSomeString;</span><br><span class="line">&#125;</span><br><span class="line">- (<span class="hljs-keyword">void</span>)setSomeString:(<span class="hljs-built_in">NSString</span> *)someString &#123;</span><br><span class="line">    <span class="hljs-built_in">dispatch_sync</span>(_syncQueue, ^&#123;</span><br><span class="line">          _someString = someString;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
</ol>
<p>思路是：把设置操作与获取操作都安排在序列化的队列里执行，这样的话，所有针对属性的访问操作就都同步了。全部加锁任务都在GCD中处理。<br>并发队列（concurrent queue）<br>栅栏（barrier）<br>在队列中，栅栏块必须单独执行，不能与其他块并行。这只对并发队列有意义，因为串行队列中的块总是按顺序逐个来执行的。并发队列如果发现接下来要处理的块是个栅栏块，那么久一直要等栅栏块执行过后，再按正常方式继续向下处理。</p>
<h5 id="要点"><a href="#要点" class="headerlink" title="要点"></a>要点</h5><ul>
<li>派发队列可用来表述同步语义（synchronization semantic），这种做法要比使用@synchronized 块或 NSLock 对象更简单。</li>
<li>将同步与异步派发结合起来，可以实现与普通加锁机制一样的同步行为，而这么做却不会阻塞执行异步派发的线程。</li>
<li>使用同步队列及栅栏块，可以令同步行为更加高效。</li>
</ul>
<h4 id="42-多用GCD，少用-performSelector-系列方法"><a href="#42-多用GCD，少用-performSelector-系列方法" class="headerlink" title="42.多用GCD，少用 performSelector 系列方法"></a>42.多用GCD，少用 performSelector 系列方法</h4><ol>
<li>performSelector 可能内存泄漏<br> performSelector 调用了一个方法。编译器并不知道将要调用的选择子是什么，因此，也就不了解其方法签名及返回值，甚至连是否有返回值都不清楚。而且，由于编译器不知道方法名，所以就没办法运用ARC的内存管理规则来判定返回值是不是应该释放。鉴于此，ARC采用了比较谨慎的做法，就是不添加释放操作。然而这么做可能导致内存泄漏，因为方法在返回对象时可能已经将其保留了。</li>
<li>有局限性。</li>
</ol>
<ul>
<li>返回值只能是void或对象类型。</li>
<li>参数类型是id，所以只能传入对象。此外，最多只能接受两个参数。</li>
</ul>
<p>dispatch_sync<br>dispatch_async<br>dispatch_after</p>
<h5 id="要点-1"><a href="#要点-1" class="headerlink" title="要点"></a>要点</h5><ul>
<li>performSelector 系列方法在内存管理方面容易有疏忽。它无法确定将要执行的选择子具体是什么，因而ARC编译器也就无法插入适当的内存管理方法。</li>
<li>performSelector 系列方法所能处理的选择子太过局限了，选择子的返回值类型及发送给方法的参数个数都收到限制。</li>
<li>如果想把任务放在另一个线程上执行，那么最好不要用 performSelector 系列方法，而是应该把任务封装到块里，然后调用大中枢派发机制的相关方法来实现。</li>
</ul>
<h4 id="43-掌握GCD及操作队列的使用时机"><a href="#43-掌握GCD及操作队列的使用时机" class="headerlink" title="43.掌握GCD及操作队列的使用时机"></a>43.掌握GCD及操作队列的使用时机</h4><h5 id="GCD与NSOperationQueue"><a href="#GCD与NSOperationQueue" class="headerlink" title="GCD与NSOperationQueue"></a>GCD与NSOperationQueue</h5><ul>
<li>操作队列在底层是用GCD来实现的。</li>
<li>GCD是纯C的API，而操作队列则是Objective-C的对象。</li>
<li>在GCD中，任务用块来表示，而块是一个轻量级的数据结构。预支相反，“操作”（operation）则是个更为重量级的Objective-C对象。</li>
</ul>
<h5 id="使用NSOperation及NSOperationQueue的好处："><a href="#使用NSOperation及NSOperationQueue的好处：" class="headerlink" title="使用NSOperation及NSOperationQueue的好处："></a>使用NSOperation及NSOperationQueue的好处：</h5><ul>
<li>取消某个操作。运行任务之前，可以在NSOperation对象上调用cancel方法，该方法会设置对象内的标志位，用以表明此任务不需执行，不过，已经启动的任务无法取消。若是不使用操作队列，而是把块安排在GCD队列，那就无法取消了。开发者可以在应用程序层自己来实现取消功能，不过这样做需要编写很多代码，而那些代码其实已经由操作队列实现好了。</li>
<li>指定操作间的依赖关系。</li>
<li>通过键值观察机制监控NSOperation对象的属性。如isCancelled、isFinished。如果想在某个任务变更其状态时得到通知，或是想用比GCD更为精细的方式来控制所要执行的任务，那么键值观察机制会很有用。</li>
<li>指定操作的优先级。GCD只有队列的优先级，没有任务的优先级。NSOpetation对象也有线程优先级，这决定了运行此操作的线程处在何种优先级上。GCD可以实现此功能，然而操作队列更简单，只需设置一个属性。</li>
<li>重用NSOperation对象。<br>系统的NSNotificationCenter API选用了操作队列而非派发队列，开发者可通过其中的方法来注册监听器，以便在发生相关事件时得到通知，而这个方法接受的参数是块，不是选择子。</li>
</ul>
<h5 id="要点-2"><a href="#要点-2" class="headerlink" title="要点"></a>要点</h5><ul>
<li>在解决多线程与任务管理问题时，派发队列并非唯一方案。</li>
<li>操作队列提供了一套高层的Objective-C API，能实现纯GCD所具备的绝大部分功能，而且还能完成一些更为复杂的操作，那些操作若改用GCD来实现，则需另外编写代码。</li>
</ul>
<h4 id="44-通过-Dispatch-Group-机制，根据系统资源状况来执行任务"><a href="#44-通过-Dispatch-Group-机制，根据系统资源状况来执行任务" class="headerlink" title="44.通过 Dispatch Group 机制，根据系统资源状况来执行任务"></a>44.通过 Dispatch Group 机制，根据系统资源状况来执行任务</h4><h5 id="要点-3"><a href="#要点-3" class="headerlink" title="要点"></a>要点</h5><ul>
<li>一系列任务可归入一个 dispatch group 之中。开发者可以在这组任务执行完毕时获得通知。</li>
<li>通过dispatch group，可以在并发式派发队列里同时执行多项任务。此时GCD会根据系统资源状况来调度这些并发执行的任务。开发者若自己来实现此功能，则需编写大量代码。</li>
</ul>
<h4 id="45-使用dispatch-once来执行只需运行一次的线程安全代码"><a href="#45-使用dispatch-once来执行只需运行一次的线程安全代码" class="headerlink" title="45. 使用dispatch_once来执行只需运行一次的线程安全代码"></a>45. 使用dispatch_once来执行只需运行一次的线程安全代码</h4><h5 id="实现单例"><a href="#实现单例" class="headerlink" title="实现单例"></a>实现单例</h5><figure class="highlight objc hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">+ (<span class="hljs-keyword">id</span>)sharedInstance &#123;</span><br><span class="line">    <span class="hljs-keyword">static</span> EOCClass *sharedInstance = <span class="hljs-literal">nil</span>;</span><br><span class="line">    <span class="hljs-keyword">static</span> <span class="hljs-built_in">dispatch_once_t</span> onceToken;</span><br><span class="line">    <span class="hljs-built_in">dispatch_once</span>(&amp;onceToken, ^&#123;</span><br><span class="line">          sharedInstance = [[<span class="hljs-keyword">self</span> alloc] init];</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="hljs-keyword">return</span> sharedInstance;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h5 id="要点-4"><a href="#要点-4" class="headerlink" title="要点"></a>要点</h5><ul>
<li>经常需要编写“只需执行一次的线程安全代码”（thread-safe single-code execution）。通过GCD所提供的 dispatch_once 函数，很容易就能实现此功能。</li>
<li>标记应该声明在 static 或 global 作用域中，这样的话，在把只需执行一次的块传给 dispatch_once 函数时，传进去的标记也是相同的。</li>
</ul>
<h4 id="46-不要使用-dispatch-get-current-queue"><a href="#46-不要使用-dispatch-get-current-queue" class="headerlink" title="46.不要使用 dispatch_get_current_queue"></a>46.不要使用 dispatch_get_current_queue</h4><h5 id="要点-5"><a href="#要点-5" class="headerlink" title="要点"></a>要点</h5><ul>
<li>dispatch_get_current_queue 函数的行为常常与开发者所预期的不同。此函数已经废弃，只应做调试之用。</li>
<li>由于派发队列是按层级来组织的，所以无法单用某个队列对象来描述“当前队列”这一概念。</li>
<li>dispatch_get_current_queue 函数用于解决由不可重入的代码所引发的死锁，然而能用此函数解决的问题，通常也能改用“队列特定数据”来解决。</li>
</ul>

        </div>
        
        <div class="level is-size-7 is-uppercase">
            <div class="level-start">
                <div class="level-item">
                    <span class="is-size-6 has-text-grey has-mr-7">#</span>
                    <a class="has-link-grey -link" href="/tags/Effective-Objective-C-2-0/">Effective Objective-C 2.0</a>
                </div>
            </div>
        </div>
        
        
        
    </div>
</div>





<div class="card card-transparent">
    <div class="level post-navigation is-flex-wrap is-mobile">
        
        <div class="level-start">
            <a class="level level-item has-link-grey  article-nav-prev" href="/EffectiveObjectiveCPart7/">
                <i class="level-item fas fa-chevron-left"></i>
                <span class="level-item">Effective Objective-C 2.0（第7章）</span>
            </a>
        </div>
        
        
        <div class="level-end">
            <a class="level level-item has-link-grey  article-nav-next" href="/EffectiveObjectiveCPart5/">
                <span class="level-item">Effective Objective-C 2.0（第5章）</span>
                <i class="level-item fas fa-chevron-right"></i>
            </a>
        </div>
        
    </div>
</div>



<div class="card">
    <div class="card-content">
        <h3 class="title is-5 has-text-weight-normal">评论</h3>
        
<div id="comment-container"></div>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gitalk@1.6.0/dist/gitalk.css">
<script src="https://cdn.jsdelivr.net/npm/gitalk@1.6.0/dist/gitalk.min.js"></script>

<script>
    var gitalk = new Gitalk({
        clientID: '7cf9ae5a9ae4228f59ef',
        clientSecret: 'a60824892847d86dab15dae00b18a8227f9af769',
        id: '10fb6e3fc08de544291e5e2405483bc1',
        repo: 'Comments',
        owner: 'objcvip',
        admin: "objcvip",
        createIssueManually: false,
        distractionFreeMode: true
    })
    gitalk.render('comment-container')
</script>

    </div>
</div>
</div>
                
                
            </div>
        </div>
    </section>
    <footer class="footer">
    <div class="container">
        <div class="level">
            <div class="level-start has-text-centered-mobile">
                <a class="footer-logo is-block has-mb-6" href="/">
                
                    <img src="/images/logo.svg" alt="Effective Objective-C 2.0（第6章）" height="28">
                
                </a>
                <p class="is-size-7">
                &copy; 2020 ObjC.vip&nbsp;
                All rights reserved.
                
                </p>
            </div>
            <div class="level-end">
            
            </div>
        </div>
    </div>
</footer>
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/moment@2.22.2/min/moment-with-locales.min.js"></script>
<script>moment.locale("zh-CN");</script>

<script>
var IcarusThemeSettings = {
    article: {
        highlight: {
            clipboard: true,
            fold: 'unfolded'
        }
    }
};
</script>


    <script src="https://cdn.jsdelivr.net/npm/clipboard@2.0.4/dist/clipboard.min.js" defer></script>



    
    
    
    <script src="/js/animation.js"></script>
    

    
    
    
    <script src="https://cdn.jsdelivr.net/npm/lightgallery@1.6.8/dist/js/lightgallery.min.js" defer></script>
    <script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js" defer></script>
    <script src="/js/gallery.js" defer></script>
    

    
    

<div id="outdated">
    <h6>Your browser is out-of-date!</h6>
    <p>Update your browser to view this website correctly. <a id="btnUpdateBrowser" href="http://outdatedbrowser.com/">Update
            my browser now </a></p>
    <p class="last"><a href="#" id="btnCloseUpdateBrowser" title="Close">&times;</a></p>
</div>
<script src="https://cdn.jsdelivr.net/npm/outdatedbrowser@1.1.5/outdatedbrowser/outdatedbrowser.min.js" defer></script>
<script>
    document.addEventListener("DOMContentLoaded", function () {
        outdatedBrowser({
            bgColor: '#f25648',
            color: '#ffffff',
            lowerThan: 'flex'
        });
    });
</script>


    
    
<script src="https://cdn.jsdelivr.net/npm/mathjax@2.7.5/unpacked/MathJax.js?config=TeX-MML-AM_CHTML" defer></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
    MathJax.Hub.Config({
        'HTML-CSS': {
            matchFontHeight: false
        },
        SVG: {
            matchFontHeight: false
        },
        CommonHTML: {
            matchFontHeight: false
        },
        tex2jax: {
            inlineMath: [
                ['$','$'],
                ['\\(','\\)']
            ]
        }
    });
});
</script>

    
    

<a id="back-to-top" title="回到顶端" href="javascript:;">
    <i class="fas fa-chevron-up"></i>
</a>
<script src="/js/back-to-top.js" defer></script>


    
    

    
    
    
    

    
    
    
    
    


<script src="/js/main.js" defer></script>

    
    <div class="searchbox ins-search">
    <div class="searchbox-container ins-search-container">
        <div class="searchbox-input-wrapper">
            <input type="text" class="searchbox-input ins-search-input" placeholder="想要查找什么..." />
            <span class="searchbox-close ins-close ins-selectable"><i class="fa fa-times-circle"></i></span>
        </div>
        <div class="searchbox-result-wrapper ins-section-wrapper">
            <div class="ins-section-container"></div>
        </div>
    </div>
</div>
<script>
    (function (window) {
        var INSIGHT_CONFIG = {
            TRANSLATION: {
                POSTS: '文章',
                PAGES: '页面',
                CATEGORIES: '分类',
                TAGS: '标签',
                UNTITLED: '(无标题)',
            },
            CONTENT_URL: '/content.json',
        };
        window.INSIGHT_CONFIG = INSIGHT_CONFIG;
    })(window);
</script>
<script src="/js/insight.js" defer></script>
<link rel="stylesheet" href="/css/search.css">
<link rel="stylesheet" href="/css/insight.css">
    
</body>
</html>